import java.util.ArrayDeque;
import java.util.Queue;

/*
 * TCSS 422 Process Control Simulator
 * Group Money: Al McKenzie, Michael Pitts, Taylor Zielske
 */

/**
 * A device that can provide Input/Output services to Processes.
 * @author Michael Pitts
 * @version Dec 8, 2011
 */
public class IODevice {
	
	/** The CPU for this simulation. */
	private final CPU my_cpu;
	
	/** The name of this device. */
	private final String my_name;
	
	/** The processes waiting for this IODevice. */
	private final Queue<Process> my_waiting_process;
	
	/** Stores if the IODevice is ready for service. */
	private boolean my_ready;
	
	/**
	 * Creates a new IODevice for this simulation.
	 * @param the_name is the name of this Device.
	 * @param the_CPU is the CPU for this simulation.
	 */
	public IODevice(final String the_name, final CPU the_CPU) {
		my_cpu = the_CPU;
		my_name = the_name;
		my_waiting_process = new ArrayDeque<Process>();
		my_ready = false;
	}
	
	/**
	 * If this device is ready to service a process.
	 * @return true if this device is ready, false otherwise.
	 */
	public final boolean ready() {
		return my_ready;
	}
	
	/**
	 * Gets which process will be serviced next.
	 * @return the next process to get service from this device.
	 */
	public final Process nextToService() {
		return my_waiting_process.peek();
	}
	
	/**
	 * Performs the requested IO service for the given Process if the given Process
	 * is next up for service and the Device is ready.
	 * Pre: read() == true && the_process.equals(nextToService())
	 * @param the_process is the process to service.
	 * @return true if the process was serviced, false otherwise.
	 */
	public boolean service(final Process the_process) {
		if (ready() && the_process.equals(nextToService())) {
			my_ready = false;
			my_waiting_process.remove();
			Main.out().println(my_name + " servicing " + the_process);
			if (!my_waiting_process.isEmpty()){
				prepNext();
			}
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Requests, on the behalf of the given Process service from this Device.
	 * @param the_process is the process requesting IO service.
	 */
	public void requestService(final Process the_process) {
		Main.out().println(the_process.toString() +" is requesting service from " + my_name);
		my_waiting_process.add(the_process);
		the_process.setStatus(Status.BLOCKED);
		if (my_waiting_process.size() == 1) {
			prepNext();
		}
	}
	
	/**
	 * Prepares for the next IO service request.
	 */
	private void prepNext() {
		final Thread thread = new Thread(getUpdate());
		thread.start();
	}

	/**
	 * Gets the name of this particular IO Device.
	 * @return the string name of this device.
	 */
	public String toString() {
		return my_name;
	}
	
	/**
	 * Gets a Runnable that runs the update method.
	 * @return a new Runnable that updates this device later.
	 * @see IODevice#update()
	 */
	private Runnable getUpdate() {
		return new Runnable() {
			@Override public void run() {
				try {
					Thread.sleep(getWait());
				} catch (InterruptedException e) {
					// Should not happen, otherwise very fast IO.
					e.printStackTrace();
				}
				update();
			}
		};
	}
	
	/**
	 * Gets how long an IO request must wait.
	 * @return the wait time in milliseconds.
	 */
	private long getWait() {
		return (Math.round(Math.random() * 4 - 0.499) + 2) * Main.DEFAULT_WAIT_TIME;
	}
	
	/**
	 * The method that updates the device as ready to service a Process.
	 */
	private synchronized void update() {
		my_ready = true;
		my_cpu.addInterrupt(new Interrupt(this));
		my_waiting_process.peek().setStatus(Status.WAITING);
	}

}
